package com.custom.action.joiner;

import com.custom.action.joiner.interfaces.*;
import com.custom.action.util.DbUtil;
import com.custom.comm.enums.*;
import com.custom.comm.exceptions.CustomCheckException;
import com.custom.comm.utils.AssertUtil;
import com.custom.comm.utils.Constants;
import com.custom.comm.utils.CustomUtil;
import com.custom.comm.utils.lambda.SFunction;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.IntStream;

/**
 * @author Xiao-Bai
 * @since 2023/4/30 18:42
 */
@SuppressWarnings("unchecked")
public abstract class AbstractJoinConditionWrapper<T, Children> extends JoinConditionAssembly<T, Children>
        implements SelectColumn<Children>, SqlFunction<Children>, JoinCondition<Children>, LambdaJoinFunc<Children> {


    /**
     * 该方法仅仅为了隔断join，以便观看代码
     */
    public Children where() {
        return childrenClass;
    }

    /**
     * 该方法仅仅未了隔断select，以便观看代码
     */
    public Children fromPrimaryTable() {
        return childrenClass;
    }

    @Override
    public Children or(boolean condition, Children wrapper) {
        return spliceCondition(condition, false, (JoinConditionWrapper<T>) wrapper);
    }

    @Override
    public Children or(boolean condition, Consumer<Children> consumer) {
        return mergeConsumerCondition(condition, false, consumer);
    }

    @Override
    public Children or(boolean condition) {
        addSqlAssembly(condition, () -> {
            setCurrAppendSymbol(Constants.OR);
            return Constants.EMPTY;
        });
        return childrenClass;
    }

    @Override
    public Children and(boolean condition, Children wrapper) {
        return spliceCondition(condition, true, (JoinConditionWrapper<T>) wrapper);
    }

    @Override
    public Children and(boolean condition, Consumer<Children> consumer) {
        return mergeConsumerCondition(condition, true, consumer);
    }

    @Override
    public <Any> Children eq(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.EQUALS, column, val);
    }

    @Override
    public <Any> Children ne(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.NOT_EQUALS, column, val);
    }

    @Override
    public <Any> Children ge(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.GREATER_THAN_EQUALS, column, val);
    }

    @Override
    public <Any> Children le(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.LESS_THAN_EQUALS, column, val);
    }

    @Override
    public <Any> Children lt(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.LESS_THAN, column, val);
    }

    @Override
    public <Any> Children gt(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapter(condition, DbSymbol.GREATER_THAN, column, val);
    }

    @Override
    public <Any> Children in(boolean condition, SFunction<Any, ?> column, Collection<?> val) {
        return adapterIn(condition, DbSymbol.IN, column, val);
    }

    @Override
    public <Any> Children inSql(boolean condition, SFunction<Any, ?> column, String inSql, Object... params) {
        return adapterIn(condition, DbSymbol.IN, column, params);
    }

    @Override
    public <Any> Children notIn(boolean condition, SFunction<Any, ?> column, Collection<?> val) {
        return adapterIn(condition, DbSymbol.NOT_IN, column, val);
    }

    @Override
    public <Any> Children notInSql(boolean condition, SFunction<Any, ?> column, String inSql, Object... params) {
        return adapterIn(condition, DbSymbol.NOT_IN, column, params);
    }

    @Override
    public Children exists(boolean condition, String existsSql) {
        return addSqlAssembly(condition, () -> DbUtil.applyExistsCondition(this.getCurrAppendSymbol(), DbSymbol.EXISTS, existsSql));
    }

    @Override
    public Children notExists(boolean condition, String notExistsSql) {
        return addSqlAssembly(condition, () -> DbUtil.applyExistsCondition(this.getCurrAppendSymbol(), DbSymbol.NOT_EXISTS, notExistsSql));
    }

    @Override
    public <Any> Children like(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapterLike(condition, DbSymbol.LIKE, SqlLike.LIKE, column, val);
    }

    @Override
    public <Any> Children notLike(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapterLike(condition, DbSymbol.NOT_LIKE, SqlLike.LIKE, column, val);
    }

    @Override
    public <Any> Children likeLeft(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapterLike(condition, DbSymbol.LIKE, SqlLike.LEFT, column, val);
    }

    @Override
    public <Any> Children likeRight(boolean condition, SFunction<Any, ?> column, Object val) {
        return adapterLike(condition, DbSymbol.LIKE, SqlLike.RIGHT, column, val);
    }

    @Override
    public <Any> Children between(boolean condition, SFunction<Any, ?> column, Object val1, Object val2) {
        return adapterBetween(condition, DbSymbol.BETWEEN, column, val1, val2);
    }

    @Override
    public <Any> Children notBetween(boolean condition, SFunction<Any, ?> column, Object val1, Object val2) {
        return adapterBetween(condition, DbSymbol.NOT_BETWEEN, column, val1, val2);
    }

    @Override
    public <Any> Children isNull(boolean condition, SFunction<Any, ?> column) {
        return addSqlAssembly(condition, () -> DbUtil.applyIsNullCondition(this.getCurrAppendSymbol(),
                funcToSqlColumn(column), DbSymbol.IS_NULL));
    }

    @Override
    public <Any> Children isNotNull(boolean condition, SFunction<Any, ?> column) {
        return addSqlAssembly(condition, () -> DbUtil.applyIsNullCondition(this.getCurrAppendSymbol(),
                funcToSqlColumn(column), DbSymbol.IS_NOT_NULL));
    }

    @Override
    public <Any> Children select(SFunction<Any, ?>... columns) {
        collectSelect(columns);
        return childrenClass;
    }

    @Override
    public <A, B> Children selectAs(SFunction<A, ?> column, SFunction<B, ?> alias) {
        collectSelect(column, alias);
        return childrenClass;
    }

    @Override
    public Children selectPrimary() {
        return selectAll(getEntityClass());
    }

    @Override
    public <Any> Children selectAll(Class<Any> anyClass) {
        collectSelectAllColumn(anyClass);
        return childrenClass;
    }

    @Override
    public <Any> Children groupBy(SFunction<Any, ?>... columns) {
        collectGroupBy(columns);
        return childrenClass;
    }

    @Override
    public Children having(boolean condition, String havingSql, Object... params) {
        collectHaving(condition, () -> {
            addParams(params);
            return havingSql;
        });
        return childrenClass;
    }

    @Override
    public Children pageParams(Integer pageIndex, Integer pageSize) {
        AssertUtil.isTrue((Objects.nonNull(pageIndex) && Objects.nonNull(pageSize)),
                () -> new CustomCheckException("Missing paging parameter：pageIndex：%s, pageSize：%s", pageIndex, pageSize));
        setPageParams(pageIndex, pageSize);
        return childrenClass;
    }

    @Override
    public <Any> Children orderByAsc(boolean condition, SFunction<Any, ?>... columns) {
        collectOrderBy(condition, SqlOrderBy.ASC, columns);
        return childrenClass;
    }

    @Override
    public <Any> Children orderByDesc(boolean condition, SFunction<Any, ?>... columns) {
        collectOrderBy(condition, SqlOrderBy.DESC, columns);
        return childrenClass;
    }

    @Override
    public <Any> Children orderByAsc(boolean condition, SFunction<Any, ?> column) {
        collectOrderBy(condition, SqlOrderBy.ASC, column);
        return childrenClass;
    }

    @Override
    public <Any> Children orderByDesc(boolean condition, SFunction<Any, ?> column) {
        collectOrderBy(condition, SqlOrderBy.DESC, column);
        return childrenClass;
    }


    private <Any> Children adapterBetween(boolean condition, DbSymbol symbol, SFunction<Any, ?> func, Object val1, Object val2) {
        if (!CustomUtil.isBasicType(val1) || !CustomUtil.isBasicType(val2)) {
            throw new IllegalArgumentException("val1 or val2 can only be basic types");
        }
        return addSqlAssembly(condition, () -> {
            String condPrefix = DbUtil.applyBetweenCondition(this.getCurrAppendSymbol(),
                    funcToSqlColumn(func), symbol);
            addParams(val1, val2);
            return condPrefix;
        });
    }

    private <Any> Children adapterLike(boolean condition, DbSymbol symbol, SqlLike like, SFunction<Any, ?> column, Object val) {
       return addSqlAssembly(condition, () -> {
           String condPrefix = DbUtil.applyCondition(this.getCurrAppendSymbol(),
                   funcToSqlColumn(column), symbol,
                   SqlLike.sqlLikeConcat(like));
           addParams(val);
           return condPrefix;
       });
    }

    private <Any> Children adapterIn(boolean condition, DbSymbol symbol, SFunction<Any, ?> column, Object val) {
        return addSqlAssembly(condition, () -> {
            StringJoiner suffix = new StringJoiner(Constants.SEPARATOR_COMMA_2);
            if (val instanceof Array) {
                int len = Array.getLength(val);
                for (int i = 0; i < len; i++) {
                    suffix.add(Constants.QUEST);
                    addParams(Array.get(val, i));
                }
            }else if (val instanceof Collection) {
                Collection<?> objects = (Collection<?>) val;
                addParams(objects);
                IntStream.range(0, objects.size()).forEach(x -> suffix.add(Constants.QUEST));
            }
            return DbUtil.applyInCondition(this.getCurrAppendSymbol(),
                    funcToSqlColumn(column), symbol, suffix.toString());

        });
    }

    private <Any> Children adapter(boolean condition, DbSymbol symbol, SFunction<Any, ?> column, Object val) {
        return addSqlAssembly(condition, () -> {
            String condPrefix = DbUtil.applyCondition(this.getCurrAppendSymbol(),
                    funcToSqlColumn(column), symbol);
            addParams(val);
            return condPrefix;
        });
    }

    private <A, B> Children adapterOn(boolean condition, DbSymbol symbol, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        collectOnCondition(condition, cache -> {
            String left = funcToSqlColumn(cache, aColumn);
            String right = funcToSqlColumn(cache, bColumn);
            return DbUtil.applyCondition(this.getCurrAppendSymbol(), left, symbol, right);
        });
        return childrenClass;
    }


    @Override
    public <A, B> Children eq(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.EQUALS, aColumn, bColumn);
    }

    @Override
    public <A, B> Children ne(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.NOT_EQUALS, aColumn, bColumn);
    }

    @Override
    public <A, B> Children ge(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.GREATER_THAN_EQUALS, aColumn, bColumn);
    }

    @Override
    public <A, B> Children gt(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.GREATER_THAN, aColumn, bColumn);
    }

    @Override
    public <A, B> Children le(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.LESS_THAN_EQUALS, aColumn, bColumn);
    }

    @Override
    public <A, B> Children lt(boolean condition, SFunction<A, ?> aColumn, SFunction<B, ?> bColumn) {
        return adapterOn(condition, DbSymbol.LESS_THAN, aColumn, bColumn);
    }

    /**
     * sql aggregate
     */
    private <Any, Pro> Children adapterAggregate(boolean condition, SqlAggregate aggregate,
                                                 boolean distinct, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        if (condition) {
            collectSelect(aggregate, distinct, column, alias);
        }
        return childrenClass;
    }

    @Override
    public <Any> Children sum(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.SUM, false, column, column);
    }

    @Override
    public <Any, Pro> Children sum(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.SUM, false, column, alias);
    }

    @Override
    public <Any> Children sumDistinct(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.SUM, true, column, column);
    }

    @Override
    public <Any, Pro> Children sumDistinct(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.SUM, true, column, alias);
    }

    @Override
    public <Any> Children avg(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.AVG, false, column, column);
    }

    @Override
    public <Any, Pro> Children avg(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.AVG, false, column, alias);
    }

    @Override
    public <Any> Children avgDistinct(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.AVG, true, column, column);
    }

    @Override
    public <Any, Pro> Children avgDistinct(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.AVG, true, column, alias);
    }

    @Override
    public <Any> Children count(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.COUNT, false, column, column);
    }

    @Override
    public <Any, Pro> Children count(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.COUNT, false, column, alias);
    }

    @Override
    public <Any> Children countDistinct(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.COUNT, true, column, column);
    }

    @Override
    public <Any, Pro> Children countDistinct(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.COUNT, true, column, alias);
    }

    @Override
    public <Any> Children max(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.MAX, false, column, column);
    }

    @Override
    public <Any, Pro> Children max(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.MAX, false, column, alias);
    }

    @Override
    public <Any> Children maxDistinct(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.MAX, true, column, column);
    }

    @Override
    public <Any, Pro> Children maxDistinct(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.MAX, true, column, alias);
    }

    @Override
    public <Any> Children min(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.MIN, false, column, column);
    }

    @Override
    public <Any, Pro> Children min(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.MIN, false, column, alias);
    }

    @Override
    public <Any> Children minDistinct(boolean condition, SFunction<Any, ?> column) {
        return adapterAggregate(condition, SqlAggregate.MIN, true, column, column);
    }

    @Override
    public <Any, Pro> Children minDistinct(boolean condition, SFunction<Any, ?> column, SFunction<Pro, ?> alias) {
        return adapterAggregate(condition, SqlAggregate.MIN, true, column, alias);
    }
}
